/**
 * 
 */
package edu.hm.compiler.interpreter.unifier;

import java.util.ArrayList;
import java.util.List;

import edu.hm.compiler.parser.grammar.BodyOption;
import edu.hm.compiler.parser.grammar.Head;
import edu.hm.compiler.parser.grammar.Rule;
import edu.hm.compiler.parser.grammar.predicate.Predicate;
import edu.hm.compiler.parser.grammar.predicate.PredicateSymbol;
import edu.hm.compiler.parser.grammar.term.Term;
import edu.hm.compiler.parser.grammar.term.TermCall;
import edu.hm.compiler.parser.grammar.term.TermVariable;
import edu.hm.compiler.parser.grammar.variable.Variable;

/**
 * @author Saruman
 */
public class Unification
{
    private final Variable var;
    private final Term     term;

    /**
     * @param var
     * @param term
     */
    public Unification( final Variable var, final Term term )
    {
        this.var = var;
        this.term = term;
    }

    /**
     * @param other
     * @return
     * @throws UnificationException
     */
    Unification unify( final Unification other )
    {
        return new Unification( other.var, this.unify( other.term ) );
    }

    /**
     * @param rule
     * @return
     */
    public Rule unify( final Rule rule )
    {
        final Predicate predicate = rule.getHead().getPredicate();
        final Predicate newPredicate = this.unify( predicate );

        final List< Predicate > predicateList = rule.getBodyOption().getPredicateList();
        final List< Predicate > newPredicateList = new ArrayList<>();
        for( final Predicate p: predicateList )
            newPredicateList.add( this.unify( p ) );

        return new Rule( new Head( newPredicate ), new BodyOption( newPredicateList ) );
    }

    /**
     * @param predicate
     * @return
     */
    public Predicate unify( final Predicate predicate )
    {
        if( predicate instanceof PredicateSymbol )
        {
            final List< Term > newTerms = new ArrayList<>();
            for( final Term t: ( (PredicateSymbol) predicate ).getTermList() )
                newTerms.add( this.unify( t ) );
            return new PredicateSymbol( predicate.getSymbol(), newTerms );
        }
        return predicate;
    }

    /**
     * @param termParam
     * @return
     */
    public Term unify( final Term termParam )
    {
        if( termParam instanceof TermCall )
        {
            final List< Term > termList = new ArrayList<>();
            final TermCall termCall = (TermCall) termParam;
            for( final Term t: termCall.getTermList() )
                termList.add( this.unify( t ) );
            return new TermCall( termCall.getSymbol(), termList );
        } else if( termParam instanceof TermVariable )
        {
            final Variable ruleVariable = ( (TermVariable) termParam ).getVariable();
            if( ruleVariable == this.var )
                return this.term;
            return termParam;
        }
        return termParam;
    }

    @Override
    public int hashCode()
    {
        return this.var.hashCode();
    }

    @Override
    public boolean equals( final Object obj )
    {
        if( obj == this )
            return true;
        if( obj == null )
            return false;
        if( obj.getClass() != this.getClass() )
            return false;
        final Unification other = (Unification) obj;
        return other.var == this.var;
    }
}
